#include "FreeWrite.h"
#include "Font.h"
#include "FontMap.h"

#include <ft2build.h>
#include FT_FREETYPE_H

namespace freewrite
{

struct FontFaceImpl
{
	FT_Face m_Impl;
	FontFaceImpl() : m_Impl(nullptr) { }
};

struct FontLibraryImpl
{
	FT_Library m_Impl;
	FontLibraryImpl() : m_Impl(nullptr) { }
};

#define ftLib (this->m_Library->m_Impl)

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

Font::Font(FontFaceImpl* pImpl, FontCache* parent)
	: m_Face(pImpl)
	, m_CharTable(4096, nullptr)
	, m_Parent(parent)
{
}

Font::~Font()
{
	delete m_Face;
}

//Fixme : Switch to UTF32
Character* Font::GetCharacter(char32_t chr, int16_t size, bool bDoNotWait)
{
	static const FT_UInt MISSING_GLYPH = 0;

	Character* ret = FindCharacter(chr, size);
	if(ret)
		return ret;

	//FIXME:
	FT_Error err = FT_Set_Pixel_Sizes(m_Face->m_Impl, 0, size);
	if(err)
	{
		FW_ERROR("Failed to set text size.");
		return nullptr;
	}
	FT_UInt glyphIndex = FT_Get_Char_Index(m_Face->m_Impl, (FT_ULong)chr);

	if(glyphIndex == MISSING_GLYPH)
	{
		Font* backUp = m_Parent->GetDefaultFont();
		if(backUp && backUp != this)
		{
			return backUp->GetCharacter(chr, size, bDoNotWait);
		}
	}

	err = FT_Load_Glyph(m_Face->m_Impl, glyphIndex, FT_LOAD_DEFAULT);
	if(err)
	{
		FW_ERROR("Failed to load glyph.");
	}

	const FT_GlyphSlot& slot = m_Face->m_Impl->glyph;

	if( slot->format != FT_GLYPH_FORMAT_BITMAP )
	{
		err = FT_Render_Glyph( slot, FT_RENDER_MODE_NORMAL);
		if(err)
		{
			FW_ERROR("Failed to render glyph.");
		}
	}

	const FT_Bitmap& ftBmp = slot->bitmap;
	const FT_Vector& advance = slot->advance;

	ret = new Character(this, chr, size,
		ftBmp.width,
		ftBmp.rows,
		slot->bitmap_left,
		slot->bitmap_top,
		(int16_t)(advance.x >> 6),
		(int16_t)(advance.y >> 6));

	InsertCharacter(ret);
	
	if(!ret->IsWhiteSpace() && ftBmp.width > 0 && ftBmp.rows > 0)
	{
		AllocationToken token;
		if(!m_Parent->Alloc(ret, &token))
		{
			FW_SAFE_DELETE(ret);
			FW_ERROR("Failed to alloc space.");
			return nullptr;
		}

		uint8_t* pSrc = static_cast<uint8_t*>(ftBmp.buffer);
		if(!m_Parent->WriteBitmap(pSrc, ftBmp.pitch, ftBmp.width, ftBmp.rows, &token, bDoNotWait))
		{
			//FIXME: Free the character
		}
	}
	return ret;
}

Character* Font::FindCharacter(char32_t chr, int16_t size)
{
	Character* iter = m_CharTable[chr % m_CharTable.size()];
	while(iter != nullptr)
	{
		if(iter->m_Char == chr && iter->m_Size == size)
			return iter;
		iter = iter->m_Next;
	}
	return nullptr;
}

void Font::InsertCharacter(Character* chr)
{
	assert(chr);
	if(chr != nullptr)
	{
		int32_t hash = chr->m_Char % m_CharTable.size();
		if(m_CharTable[hash])
		{
			chr->m_Next = m_CharTable[hash];
		}
		m_CharTable[hash] = chr;
	}
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

FontCache::FontCache(IFWTextureFactory* pTexFactory)
	: m_TextureFactory(pTexFactory)
	, m_Library(nullptr)
{
	m_Library = new FontLibraryImpl();
}

FontCache::~FontCache()
{
	FW_SAFE_DELETE(m_Library);
}

bool FontCache::Initialize()
{
	FT_Error err = FT_Init_FreeType(&m_Library->m_Impl);
	if( err )
	{
		FW_ERROR("FreeType library initialization failed.");
		return false;
	}
	return true;
}

void FontCache::CleanUp()
{
	for(auto itor = m_LoadedFonts.begin();
		itor != m_LoadedFonts.end();
		itor++)
	{
		Font* pFont = itor->second;
		FW_SAFE_DELETE(pFont);
	}
	for(auto itor = m_FontMaps.begin();
		itor != m_FontMaps.end();
		itor++)
	{
		FontMap* pMap = *itor;
		FW_SAFE_DELETE(pMap);
	}
	m_LoadedFonts.clear();

	if(m_Library->m_Impl)
	{
		FT_Done_FreeType(m_Library->m_Impl);
		m_Library->m_Impl = nullptr;
	}
}

Font* FontCache::LoadFont(const std::string& fontName)
{
	auto itor = m_LoadedFonts.find(fontName);
	if(itor != m_LoadedFonts.end())
		return itor->second;

	FT_Face face;
	FT_Error err = FT_New_Face(ftLib, fontName.c_str(), 0, &face);
	if(err)
	{
		FW_ERROR("Failed to load font.");	//	'%s', error code '%d'."), fntPath.c_str(), err);
		return nullptr;
	}

	err = FT_Select_Charmap(face, FT_Encoding::FT_ENCODING_UNICODE);
	if(err)
	{
		FW_ERROR("Select Charmap failed."); //, error code '%d'."), err);
		FT_Done_Face(face);
		return nullptr;
	}

	FontFaceImpl* fi = new FontFaceImpl();
	fi->m_Impl = face;
	Font* font = new Font(fi, this);

	m_LoadedFonts.insert(std::make_pair(fontName, font));
	return font;
}

void FontCache::SetDefaultFont(const std::string& defaultFont)
{
	if(LoadFont(defaultFont) != nullptr)
	{
		m_DefaultFontName = defaultFont;
	}
}

bool FontCache::Alloc(Character* chr, AllocationToken* token)
{
	for(int32_t i=m_FontMaps.size() - 1; i>=0; i--)
	{
		if(m_FontMaps[i]->Alloc(chr, token))
			return true;
	}

	//Alloc new map;
	IFWTexture2D* pTexture = m_TextureFactory->CreateTexture(TEXFMT_A8, FW_TEXTURE_DIM, FW_TEXTURE_DIM);
	FontMap* pNewMap = new FontMap(pTexture);
	m_FontMaps.push_back(pNewMap);

	return pNewMap->Alloc(chr, token);
}

bool FontCache::WriteBitmap(uint8_t* pSrc, uint32_t srcPitch, uint32_t width, uint32_t height, AllocationToken* token, bool bDoNotWait)
{
	return token->FontMap->WriteBitmap(pSrc, srcPitch, width, height, token, bDoNotWait);
}

////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

IFWTexture2D* Character::GetTexture() const
{
	if(IsWhiteSpace())
		return nullptr;
	return m_FontMap->GetTexture();
}

Vector4U16 Character::GetSubUVNormalized() const
{
	if(IsWhiteSpace())
	{
		return Vector4U16(0, 0, 0, 0);
	}
	return m_FontMap->CalculateSubUV(this);
}

Vector2I16 Character::GetKerning(const Character* pPrev) const
{
	if(!pPrev
		|| (m_Font != pPrev->m_Font)
		|| (m_Size != pPrev->m_Size)
		|| (!FT_HAS_KERNING(m_Font->m_Face->m_Impl)))
	{
		return Vector2I16(0, 0);
	}
	const FT_Face& face = m_Font->m_Face->m_Impl;
	FT_Error err = FT_Set_Pixel_Sizes(face, 0, m_Size);
	if(err)
	{
		FW_ERROR("Failed to set pixel size for getting kerning info.");
		return Vector2I16(0, 0);
	}

	FT_Vector delta;
	FT_UInt prevIdx = FT_Get_Char_Index(face, (FT_ULong)pPrev->m_Char);
	FT_UInt currIdx = FT_Get_Char_Index(face, (FT_ULong)m_Char);
	err = FT_Get_Kerning(face,
		prevIdx, currIdx,
		FT_KERNING_UNFITTED, &delta);

	if(err)
	{
		FW_ERROR("Failed to get kerning info.");
		return Vector2I16(0, 0);
	}
	
	return Vector2I16((int16_t)(delta.x >> 6), (int16_t)(delta.y >> 6));
}

}